/**
 * Calc
 *
 * @author Chris Pratt
 *
 * 2/26/2003
 */
package com.anodyzed.onyx.util;

public class Calc {
  private static final char NONE = (char)-1;
  private static final char SPC  = ' ';
  private static final char LEFT = '(';
  private static final char RITE = ')';
  private static final char ADD  = '+';
  private static final char SUB  = '-';
  private static final char MUL  = '*';
  private static final char DIV  = '/';
  private static final char MOD  = '%';
  private static final char NOT  = '~';
  private static final char BANG = '!';
  private static final char SHL  = '<';
  private static final char SHR  = '>';
  private static final char AND  = '&';
  private static final char OR   = '|';
  private static final char XOR  = '^';

  /**
   * Constructor
   */
  private Calc () {
  } //Calc

  /**
   * Parse the Number from the Equation
   *
   * @param equation The Equation Character Array
   * @param pos The Current Position Pointer in the Array
   * @return The Numeric Value
   */
  private static long getNum (char[] equation,int[] pos) {
    boolean not = false;
    boolean neg = false;
    int base = 10;
    long val = 0;
    while((pos[0] < equation.length) && (equation[pos[0]] == SPC)) {
      pos[0]++;
    }
    if((pos[0] < equation.length) && (equation[pos[0]] == LEFT)) {
      pos[0]++;
      return eval(equation,pos);
    } else {
      if((pos[0] < equation.length) && ((equation[pos[0]] == NOT) || (equation[pos[0]] == BANG))) {
        not = true;
        pos[0]++;
      }
      if((pos[0] < equation.length) && ((equation[pos[0]] == SUB) || (equation[pos[0]] == ADD))) {
        neg = equation[pos[0]++] == SUB;
      }
      if((pos[0] < equation.length) && (equation[pos[0]] == '0')) {
        if((++(pos[0]) < equation.length) && (equation[pos[0]] == 'x')) {
          base = 16;
          pos[0]++;
        } else if((pos[0] < equation.length) && (equation[pos[0]] == 'b')) {
          base = 2;
          pos[0]++;
        } else {
          base = 8;
        }
      }
      while((pos[0] < equation.length) && Misc.isNumeric(equation[pos[0]],base)) {
        val = (val * base) + Character.digit(equation[pos[0]++],base);
      }
      if(pos[0] < equation.length) {
        if(Character.toUpperCase(equation[pos[0]]) == 'K') {
          val *= 1024;
          pos[0]++;
        } else if(Character.toUpperCase(equation[pos[0]]) == 'M') {
          val *= 1048576;
          pos[0]++;
        } else if(Character.toUpperCase(equation[pos[0]]) == 'G') {
          val *= 1073741824;
          pos[0]++;
        }
      }
      if(not) {
        val = ~val;
      }
      if(neg) {
        val *= -1;
      }
      return val;
    }
  } //getNum

  /**
   * Get a single Equation Term
   *
   * @param equation The Equation Character Array
   * @param pos The Current Position Pointer in the Array
   * @return The term value
   */
  private static long getTerm (char[] equation,int[] pos) {
    long val = getNum(equation,pos);
    while(pos[0] < equation.length) {
      switch(getOperator(equation,pos)) {
        case LEFT:
          pos[0]++;
          return eval(equation,pos);
        case MUL:
          pos[0]++;
          val *= getNum(equation,pos);
          break;
        case MOD:
          pos[0]++;
          val %= getNum(equation,pos);
        case DIV:
          pos[0]++;
          val /= getNum(equation,pos);
          break;
        default:
          return val;
      }
    }
    return val;
  } //getTerm

  /**
   * Locate the next Operator
   *
   * @param equation The Equation Character Array
   * @param pos The Current Position Pointer in the Array
   * @return The Operator Character
   */
  private static char getOperator (char[] equation,int[] pos) {
    while(pos[0] < equation.length) {
      switch(equation[pos[0]]) {
        case SPC:
            // Skip it
          pos[0]++;
          break;
        case '{':
        case '[':
          return LEFT;
        case '}':
        case ']':
          return RITE;
        case LEFT:
        case RITE:
        case ADD:
        case SUB:
        case MUL:
        case DIV:
        case NOT:
        case BANG:
          return equation[pos[0]];
        case AND:
          if((pos[0] + 1 < equation.length) && (equation[pos[0] + 1] == AND)) {
            pos[0]++;
          }
          return AND;
        case OR:
          if((pos[0] + 1 < equation.length) && (equation[pos[0] + 1] == OR)) {
            pos[0]++;
          }
          return OR;
        case XOR:
          if((pos[0] + 1 < equation.length) && (equation[pos[0] + 1] == XOR)) {
            pos[0]++;
          }
          return XOR;
        case SHL:
          if((pos[0] + 1 < equation.length) && (equation[pos[0] + 1] == SHL)) {
            pos[0]++;
          }
          return SHL;
        case SHR:
          if((pos[0] + 1 < equation.length) && (equation[pos[0] + 1] == SHR)) {
            pos[0]++;
            if((pos[0] + 1 < equation.length) && (equation[pos[0] + 1] == SHR)) {
              pos[0]++;
            }
          }
          return SHR;
        default:
          throw new IllegalArgumentException("Parse Error at position " + pos[0] + " (" + equation[pos[0]] + ')');
      }
    }
    return NONE;
  } //getOperator

  /**
   * Evaluate the specified equation from the specified position and with the
   * given starting value
   *
   * @param val The Initial value
   * @param equation The Equation Character Array
   * @param pos The Current Position Pointer in the Array
   * @return The result of the evaluation
   */
  private static long eval (long val,char[] equation,int[] pos) {
    char op;
    while(pos[0] < equation.length) {
      switch(op = getOperator(equation,pos)) {
        case NONE:
        case RITE:
          pos[0]++;
          return val;
        case ADD:
          pos[0]++;
          val += getTerm(equation,pos);
          break;
        case SUB:
          pos[0]++;
          val -= getTerm(equation,pos);
          break;
        case SHL:
          pos[0]++;
          val <<= getTerm(equation,pos);
          break;
        case SHR:
          pos[0]++;
          val >>= getTerm(equation,pos);
          break;
        case AND:
          pos[0]++;
          val &= getTerm(equation,pos);
          break;
        case OR:
          pos[0]++;
          val |= getTerm(equation,pos);
          break;
        case XOR:
          pos[0]++;
          val ^= getTerm(equation,pos);
          break;
        default:
          throw new IllegalArgumentException("Unrecognized Operation (" + op + ") at position " + pos[0]);
      }
    }
    return val;
  } //eval

  /**
   * Evaluate the specified equation from the specified position
   *
   * @param equation The Equation Character Array
   * @param pos The Current Position Pointer in the Array
   * @return The result of the evaluation
   */
  private static long eval (char[] equation,int[] pos) {
    return eval(getTerm(equation,pos),equation,pos);
  } //eval

  /**
   * Evaluate the specified equation
   *
   * @param equation The Equation String
   * @return The result of the Evaluation
   */
  public static long eval (String equation) {
    return eval(equation.toCharArray(),new int[] {0});
  } //eval

  /**
   * Evaluate the specified equation
   *
   * @param val The Initial value of the equation
   * @param equation The Equation String
   * @return The result of the Evaluation
   */
  public static long eval (long val,String equation) {
    return eval(val,equation.toCharArray(),new int[] {0});
  } //eval

  /**
   * Evaluate the specified equation and return the answer as a string
   *
   * @param equation The Equation String
   * @return The Result of the Evaluation String
   */
  public static String toString (String equation) {
    return String.valueOf(eval(equation.toCharArray(),new int[] {0}));
  } //toString

  /**
   * Evaluate the specified equation and return the answer as a String
   *
   * @param val The Initial value of the equation
   * @param equation The Equation String
   * @return The Result of the Evaluation as a String
   */
  public static String toString (long val,String equation) {
    return String.valueOf(eval(val,equation.toCharArray(),new int[] {0}));
  } //toString

  /**
   * Round the number <code>d</code> to the nearest <code>t</code>.
   *
   * @param d The Number to be Rounded
   * @param t The target increment
   * @return <code>d</code> rounded to the nearest <code>t</code>
   */
  public static double roundToNearest (double d,double t) {
    return Math.round(d / t) * t;
  } //roundToNearest

  /**
   * Return the next value of <code>d</code> away from zero by an increment of
   * <code>t</code>.
   *
   * @param d The Number to be Ceiling'd
   * @param t The target increment
   * @return <code>d</code> ceilinged to the nearest <code>t</code>
   */
  public static double ceilToNearest (double d,double t) {
    return Math.ceil(d / t) * t;
  } //ceilToNearest

  /**
   * Return the next value of <code>d</code> closer to zero by an increment of
   * <code>t</code>.
   *
   * @param d The Number to be Floored
   * @param t The target increment
   * @return <code>d</code> floored to the nearest <code>t</code>
   */
  public static double floorToNearest (double d,double t) {
    return Math.floor(d / t) * t;
  } //floorToNearest

  /**
   * Add the supplied integers, substituting 0 for any possible null values
   *
   * @param a The first Integer
   * @param b The second Integer
   * @return Their sum
   */
  public static Integer add (Integer a,Integer b) {
    return (a != null) ? ((b != null) ? new Integer(a + b) : a) : ((b != null) ? b : null);
  } //add

  /**
   * Add the supplied double-precision floating-point numbers, substituting 0.0
   * for any possible null values
   *
   * @param a The first number
   * @param b The second number
   * @return Their sum
   */
  public static Double add (Double a,Double b) {
    return (a != null) ? ((b != null) ? new Double(a + b) : a) : ((b != null) ? b : null);
  } //add

} //*Calc
